package com.gaogzhen.service.impl;

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.RandomUtil;
import com.alicp.jetcache.Cache;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.CreateCache;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gaogzhen.domain.Account;
import com.gaogzhen.domain.CashWithdrawAuditRecord;
import com.gaogzhen.domain.CashWithdrawals;
import com.gaogzhen.domain.Config;
import com.gaogzhen.dto.UserBankDto;
import com.gaogzhen.dto.UserDto;
import com.gaogzhen.feigh.UserBankServiceFeign;
import com.gaogzhen.feigh.UserServiceFeign;
import com.gaogzhen.mapper.CashWithdrawalsMapper;
import com.gaogzhen.model.CashSellParam;
import com.gaogzhen.model.RRException;
import com.gaogzhen.service.AccountService;
import com.gaogzhen.service.CashWithdrawAuditRecordService;
import com.gaogzhen.service.CashWithdrawalsService;
import com.gaogzhen.service.ConfigService;
import com.gaogzhen.utils.CellProcessorAdaptorUtils;
import com.gaogzhen.utils.Constants;
import com.gaogzhen.utils.ReportCsvUtils;
import com.gaogzhen.utils.SqlExecuteUtils;
import com.gaogzhen.vo.CashTradeVo;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.supercsv.cellprocessor.ift.CellProcessor;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @description ${description}
* @author Administrator
* @date 2022-11-03 11:43
* @version 1.0
*/
@Service
public class CashWithdrawalsServiceImpl extends ServiceImpl<CashWithdrawalsMapper, CashWithdrawals> implements CashWithdrawalsService{

    @Autowired
    private UserServiceFeign userServiceFeign;

    @Autowired
    private UserBankServiceFeign userBankServiceFeign;

    @Autowired
    private CashWithdrawAuditRecordService cashWithdrawAuditRecordService;

    @Autowired
    private AccountService accountService;

    @Autowired
    private ConfigService configService;

    @CreateCache(name = "CASH_RECHARGE_LOCK", expire = 100, timeUnit = TimeUnit.SECONDS, cacheType = CacheType.BOTH)
    private Cache<String, Service> lock;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private Snowflake snowflake;

    @Override
    public Page<CashWithdrawals> findByPage(Page<CashWithdrawals> page, Long userId, String username, String mobile, Byte status, String numMin, String numMax, String startTime, String endTime) {
        // 按最后修改时间倒序查询
        page.addOrder(OrderItem.desc("last_update_time"));
        LambdaQueryWrapper<CashWithdrawals> queryWrapper = new LambdaQueryWrapper<>();
        // 1 携带用户信息：用户名或者手机号
        Map<Long, UserDto> userDtoMap = null;
        Set<Long> ids = null;
        if (ObjectUtils.isNotEmpty(userId) || StringUtils.isNotEmpty(username) || StringUtils.isNotEmpty(mobile)) {
            // 远程调用
            userDtoMap = userServiceFeign.getBriefUsers(ObjectUtils.isEmpty(userId) ? null : Collections.singletonList(userId), username, mobile);
            if (MapUtils.isEmpty(userDtoMap)) {
                return new Page<>();
            }
            ids = userDtoMap.keySet();
            queryWrapper.in(CollectionUtils.isNotEmpty(ids), CashWithdrawals::getUserId, ids);
        }
        // 2 没有携带
        LambdaQueryWrapper<CashWithdrawals> wrapper = queryWrapper
                .eq(ObjectUtils.isNotEmpty(status), CashWithdrawals::getStatus, status)
                .between(
                        StringUtils.isNotEmpty(numMin) && StringUtils.isNotEmpty(numMax),
                        CashWithdrawals::getNum,
                        numMin == null? null : new BigDecimal(numMin), numMax == null ? null: new BigDecimal(numMax))
                .between(
                        StringUtils.isNotEmpty(startTime) && StringUtils.isNotEmpty(endTime),
                        CashWithdrawals::getCreated,
                        startTime, endTime + " 23:59:59");

        Page<CashWithdrawals> cashWithdrawalsPage = page(page, wrapper);
        List<CashWithdrawals> records = cashWithdrawalsPage.getRecords();

        if (CollectionUtils.isNotEmpty(records)) {
            // 远程调用
            if (MapUtils.isEmpty(userDtoMap)) {
                List<Long> idList = records.stream().map(CashWithdrawals::getUserId).collect(Collectors.toList());
                userDtoMap = userServiceFeign.getBriefUsers(idList,null, null);
            }
            Map<Long, UserDto> finalUserDtoMap = userDtoMap;
            records.forEach(c -> {
                UserDto userDto = finalUserDtoMap.get(c.getUserId());
                if (userDto == null) {
                    c.setUsername(Constants.UNKNOWN_USER);
                    c.setRealName(Constants.UNKNOWN_USER);
                } else {
                    c.setUsername(userDto.getUsername());
                    c.setRealName(userDto.getRealName());
                }

            });
        }

        return cashWithdrawalsPage;
    }

    @Override
    public void exportCNYWithDrawals(Long userId, String username, String mobile, Byte status, String numMin, String numMax, String startTime, String endTime) {

        LambdaQueryWrapper<CashWithdrawals> queryWrapper = new LambdaQueryWrapper<>();
        // 1 携带用户信息：用户名或者手机号
        Map<Long, UserDto> userDtoMap = null;
        Set<Long> ids = null;
        if (ObjectUtils.isNotEmpty(userId) || StringUtils.isNotEmpty(username) || StringUtils.isNotEmpty(mobile)) {
            // 远程调用
            userDtoMap = userServiceFeign.getBriefUsers(ObjectUtils.isEmpty(userId) ? null : Collections.singletonList(userId), username, mobile);
            if (MapUtils.isEmpty(userDtoMap)) {
                return;
            }
            ids = userDtoMap.keySet();
            queryWrapper.in(CollectionUtils.isNotEmpty(ids), CashWithdrawals::getUserId, ids);
        }
        // 2 没有携带
        LambdaQueryWrapper<CashWithdrawals> wrapper = queryWrapper
                .eq(ObjectUtils.isNotEmpty(status), CashWithdrawals::getStatus, status)
                .between(
                        StringUtils.isNotEmpty(numMin) && StringUtils.isNotEmpty(numMax),
                        CashWithdrawals::getNum,
                        numMin == null? null : new BigDecimal(numMin), numMax == null ? null: new BigDecimal(numMax))
                .between(
                        StringUtils.isNotEmpty(startTime) && StringUtils.isNotEmpty(endTime),
                        CashWithdrawals::getCreated,
                        startTime, endTime + " 23:59:59");


        List<CashWithdrawals> records = list(wrapper);
        if (CollectionUtils.isEmpty(records)) {
            return;
        }

        // 远程调用
        if (MapUtils.isEmpty(userDtoMap)) {
            List<Long> idList = records.stream().map(CashWithdrawals::getUserId).collect(Collectors.toList());
            userDtoMap = userServiceFeign.getBriefUsers(idList,null, null);
        }
        Map<Long, UserDto> finalUserDtoMap = userDtoMap;
        records.forEach(c -> {
            UserDto userDto = finalUserDtoMap.get(c.getUserId());
            if (userDto == null) {
                c.setUsername(Constants.UNKNOWN_USER);
                c.setRealName(Constants.UNKNOWN_USER);
            } else {
                c.setUsername(userDto.getUsername());
                c.setRealName(userDto.getRealName());
            }

        });
        CellProcessor[] processors = new CellProcessor[] {
                CellProcessorAdaptorUtils.long2StringAdaptor(), CellProcessorAdaptorUtils.long2StringAdaptor(), null,
                CellProcessorAdaptorUtils.moneyAdaptor(),CellProcessorAdaptorUtils.moneyAdaptor(),CellProcessorAdaptorUtils.moneyAdaptor(), null, null,
                CellProcessorAdaptorUtils.timeAdaptor(null), CellProcessorAdaptorUtils.timeAdaptor(null),
                CellProcessorAdaptorUtils.statusAdaptor(Constants.CASH_WITHDRAWS_STATUS), null, null
        };
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        try {
            ReportCsvUtils.reportList(requestAttributes.getResponse(), Constants.CASH_WITHDRAWS_HEADERS, Constants.CASH_WITHDRAWS_PROPERTIES, "场外交易提现审核.csv", records, processors);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void updateWithdrawalsStatus(Long adminId, CashWithdrawAuditRecord auditRecord) {
        // 同一时刻，只允许一名员工审核,加锁
        lock.tryLockAndRun(auditRecord.getId() + "", 300, TimeUnit.SECONDS, () -> {
            Long recordId = auditRecord.getId();
            CashWithdrawals record = getById(recordId);
            if (record == null) {
                throw new RRException("提现记录不存在");
            }
            Byte status = record.getStatus();
            if (status == 1) {
                throw new RRException("审核已通过");
            }
            CashWithdrawAuditRecord auditRecord1 = new CashWithdrawAuditRecord();
            auditRecord1.setAuditUserId(adminId);
            auditRecord1.setStatus(auditRecord.getStatus());
            auditRecord1.setRemark(auditRecord.getRemark());
            byte step = (byte) (record.getStep() + 1);
            auditRecord1.setStep(step);
            auditRecord1.setOrderId(record.getId());

            SqlExecuteUtils.sqlExceptionHandler(a -> cashWithdrawAuditRecordService.save(a), auditRecord1, "新增审核记录失败");

            record.setStatus(auditRecord.getStatus());
            record.setRemark(auditRecord.getRemark());
            record.setStep(step);
            record.setAccountId(adminId);

            // 审核未通过
            if (auditRecord.getStatus() == 2) {
                SqlExecuteUtils.sqlExceptionHandler(this::updateById, record, "更新充值记录失败");
            } else {
                // 审核通过，用户账户加钱
                accountService.transferAccountAmount(adminId, record.getUserId(), record.getCoinId(), record.getNum(), record.getFee(), record.getId(), auditRecord.getRemark(), "withdrawals_out", (byte) 2);
                SqlExecuteUtils.sqlExceptionHandler(this::updateById, record, "更新充值记录失败");
            }
        });
    }

    @Override
    public Page<CashWithdrawals> findUserCashWithdrawals(Long userId, Page<CashWithdrawals> page, Byte status) {
        page.addOrder(OrderItem.desc("last_update_time"));

        return page(page, new LambdaQueryWrapper<CashWithdrawals>()
                .eq(CashWithdrawals::getUserId, userId)
                .eq(status != null, CashWithdrawals::getStatus, status)
        );
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void sell(Long userId, CashSellParam cashSellParam) {
        // 1 校验参数
        UserDto userDto = userServiceFeign.getUserById(userId);
        checkUserRelated(cashSellParam, userDto);
        Map<String, Config> configMap = configService.getByCodes("WITHDRAW_STATUS", "WITHDRAW_MAX_AMOUNT", "WITHDRAW_MIN_AMOUNT", "USDT2CNY", "WITHDRAW_POUNDAGE_RATE", "WITHDRAW_MIN_POUNDAGE");
        checkConfig(cashSellParam, configMap);
        // 校验余额
        Account account = accountService.getUserAccount(userId, "GCN");
        if (account == null) {
            throw new RRException("您输入的账户资金类型不存在");
        }
        BigDecimal balanceAmount = account.getBalanceAmount();
        BigDecimal balance = balanceAmount.subtract(cashSellParam.getNum());
        if (balance.compareTo(BigDecimal.ZERO) < 0) {
            throw new RRException("账号币种资金余额不足");
        }
        // 2 订单创建
        CashWithdrawals cashWithdrawals = new CashWithdrawals();
        // 用户信息
        cashWithdrawals.setUserId(userId);
        //银行卡信息
        UserBankDto bankDto = userBankServiceFeign.getBriefUserBank(userId);
        cashWithdrawals.setBank(bankDto.getBank());
        cashWithdrawals.setBankAddr(bankDto.getBankAddr());
        cashWithdrawals.setBankCard(bankDto.getBankCard());
        cashWithdrawals.setBankProv(bankDto.getBankProv());
        cashWithdrawals.setBankCity(bankDto.getBankCity());
        cashWithdrawals.setRealName(bankDto.getRealName());

        // 金额信息
        cashWithdrawals.setNum(cashSellParam.getNum());
        Config usdt2CNYConfig = configMap.get("USDT2CNY");

        BigDecimal amount = new BigDecimal(usdt2CNYConfig.getValue()).multiply(cashSellParam.getNum()).setScale(2, RoundingMode.HALF_UP);
        Config withdrawRateConfig = configMap.get("WITHDRAW_POUNDAGE_RATE");
        BigDecimal fee = amount.multiply(new BigDecimal(withdrawRateConfig.getValue())).setScale(2, RoundingMode.HALF_UP);
        Config minPoundageConfig = configMap.get("WITHDRAW_MIN_POUNDAGE");
        BigDecimal minPoundage = new BigDecimal(minPoundageConfig.getValue());
        if (fee.compareTo(minPoundage) < 0) {
            fee = minPoundage;
        }
        cashWithdrawals.setFee(fee);
        cashWithdrawals.setMum(amount.subtract(fee).setScale(2, RoundingMode.HALF_UP));
        //其他信息
//        String orderNo = String.valueOf(snowflake.nextId());

        cashWithdrawals.setAccountId(account.getId());
        cashWithdrawals.setCoinId(cashSellParam.getCoinId());
        cashWithdrawals.setTruename(userDto.getRealName());
        String remark = RandomUtil.randomNumbers(6);
        cashWithdrawals.setRemark(remark);
        cashWithdrawals.setStatus((byte) 0);
        cashWithdrawals.setStep((byte) 1);

        SqlExecuteUtils.sqlExceptionHandler(this::save, cashWithdrawals, "添加记录失败");
        // 3 账户余额变动
        accountService.lockUserAmount(account, cashSellParam.getNum(), "WITHDRAWALS_OUT", cashWithdrawals.getId(), cashWithdrawals.getFee());
    }

    /**
     * 获取金额
     * @param num   卖出数量
     * @return      对应金额
     */
    private BigDecimal getCashWithdrawalsAmount(BigDecimal num) {
        Config config = configService.getByCode("USDT2CNY");
        return new BigDecimal(config.getValue()).multiply(num).setScale(2, RoundingMode.HALF_UP);
    }

    private void checkUserRelated(CashSellParam cashSellParam, UserDto user) {
        // 校验手机验证码
        String validateCode = cashSellParam.getValidateCode();
        String code = String.valueOf(redisTemplate.opsForValue().get("SMS:CASH_WITHDRAWS:" + user.getMobile()));
        if (!StringUtils.equals(validateCode, code)) {
            throw new RRException("手机验证码不一致");
        }
        // 校验支付密码
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        if (!passwordEncoder.matches(cashSellParam.getPayPassword(), user.getPaypassword())) {
            throw new RRException("交易密码错误");
        }
    }
    private void checkConfig(CashSellParam cashSellParam, Map<String, Config> configMap) {

        // 校验提现状态
        Config withdrawStatusConfig =configMap.get("WITHDRAW_STATUS");
        int status = Integer.parseInt(withdrawStatusConfig.getValue());
        if (status != 1) {
            throw new RRException("当前不能提现");
        }
        // 校验最大最小提现金额
        BigDecimal num = cashSellParam.getNum();
        Config withdrawMaxAmountConfig = configMap.get("WITHDRAW_MAX_AMOUNT");
        BigDecimal withdrawMax = new BigDecimal(withdrawMaxAmountConfig.getValue());
        if (withdrawMax.compareTo(num) < 0) {
            throw new RRException("超出最大提现数量");
        }
        Config withdrawMinAmountConfig = configMap.get("WITHDRAW_MIN_AMOUNT");
        BigDecimal withdrawMin = new BigDecimal(withdrawMinAmountConfig.getValue());
        if (withdrawMin.compareTo(num) > 0) {
            throw new RRException("低于最小提现数量");
        }




    }
}
